import { randomUUID } from 'node:crypto';
import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import type sinon from 'sinon';
import type { CodeTask, CodeTaskPreset } from '#shared/codeTask/codeTask.model';
import type { CodeTaskRepository } from './codeTaskRepository';

chai.use(chaiAsPromised);

// Helper to create mock codeTask data
const createMockCodeTask = (userId: string, overrides: Partial<CodeTask> = {}): CodeTask => {
	const id = overrides.id ?? randomUUID();
	const now = Date.now();
	return {
		id: id,
		userId: userId,
		title: `Test CodeTask ${id.substring(0, 4)}`,
		instructions: 'Test instructions',
		repositorySource: 'local',
		repositoryId: '/test/repo',
		targetBranch: 'main',
		workingBranch: `codeTask/${id.substring(0, 8)}`,
		createWorkingBranch: true,
		useSharedRepos: false,
		status: 'initializing',
		createdAt: now,
		updatedAt: now,
		lastAgentActivity: now,
		error: null,
		...overrides, // Apply overrides, including potential timestamp overrides
	};
};

// Helper to create mock preset data
const createMockPreset = (userId: string, overrides: Partial<CodeTaskPreset> = {}): CodeTaskPreset => {
	const id = overrides.id ?? randomUUID();
	const now = Date.now();
	return {
		id: id,
		userId: userId,
		name: `Test Preset ${id.substring(0, 4)}`,
		config: {
			repositorySource: 'github',
			repositoryFullPath: '12345', // Mock SCM project ID
			repositoryName: 'owner/repo', // Mock repository name
			targetBranch: 'develop',
			workingBranch: 'codeTask/feature-branch',
			createWorkingBranch: true,
			useSharedRepos: true,
		},
		createdAt: now,
		updatedAt: now,
		...overrides,
	};
};

export function runCodeTaskRepositoryTests(
	createRepository: () => CodeTaskRepository,
	beforeEachHook: () => Promise<void> | void,
	afterEachHook: () => Promise<void> | void,
	// Removed parameters for currentUserStub and user objects
): void {
	let repo: CodeTaskRepository;
	// Define fixed user IDs for testing consistency
	const testUserId = 'test-user-repo-tests';
	const otherUserId = 'another-user-repo-tests'; // Must be different from testUserId
	const userWithNoItemsId = 'user-with-no-items'; // For empty list tests

	beforeEach(async () => {
		await beforeEachHook(); // Run implementation-specific setup (e.g., clear DB/memory)
		repo = createRepository();
	});

	afterEach(async () => {
		// sinon.restore(); // Removed: Stub restoration is handled by the calling test file's setup
		await afterEachHook(); // Run implementation-specific teardown
	});

	describe('CodeTask CRUD', () => {
		it('should create a new codeTask and retrieve it', async () => {
			const codeTaskData = createMockCodeTask(testUserId);
			const codeTaskId = await repo.createCodeTask(codeTaskData);
			expect(codeTaskId).to.equal(codeTaskData.id);

			const retrievedCodeTask = await repo.getCodeTask(testUserId, codeTaskId);
			expect(retrievedCodeTask).to.not.be.null;
			// Compare essential fields, allow timestamps to differ slightly if generated by DB
			expect(retrievedCodeTask?.id).to.equal(codeTaskData.id);
			expect(retrievedCodeTask?.userId).to.equal(testUserId);
			expect(retrievedCodeTask?.title).to.equal(codeTaskData.title);
			expect(retrievedCodeTask?.status).to.equal('initializing');
			expect(retrievedCodeTask?.createdAt).to.be.a('number');
		});

		it('should return null when retrieving a non-existent codeTask', async () => {
			const nonExistentId = randomUUID();
			const retrievedCodeTask = await repo.getCodeTask(testUserId, nonExistentId);
			expect(retrievedCodeTask).to.be.null;
		});

		it('should return null when retrieving a codeTask belonging to another user', async () => {
			const codeTaskData = createMockCodeTask(otherUserId);
			const codeTaskId = await repo.createCodeTask(codeTaskData);

			const retrievedCodeTask = await repo.getCodeTask(testUserId, codeTaskId);
			expect(retrievedCodeTask).to.be.null;
		});

		it.skip('should list codeTasks for a user, ordered by updatedAt descending', async () => {
			const codeTask1 = createMockCodeTask(testUserId, { createdAt: Date.now() - 5000, updatedAt: Date.now() - 2000 });
			const codeTask2 = createMockCodeTask(testUserId, { createdAt: Date.now() - 6000, updatedAt: Date.now() - 1000 }); // Newer update with older creation
			const codeTaskOtherUser = createMockCodeTask(otherUserId);

			await repo.createCodeTask(codeTask1);
			await repo.createCodeTask(codeTask2);
			await repo.createCodeTask(codeTaskOtherUser);

			const userCodeTasks = await repo.listCodeTasks(testUserId);
			expect(userCodeTasks).to.be.an('array').with.lengthOf(2);
			expect(userCodeTasks[0].id).to.equal(codeTask2.id); // Newest first
			expect(userCodeTasks[1].id).to.equal(codeTask1.id);
		});

		it('should return an empty array when listing codeTasks for a user with no codeTasks', async () => {
			// No need to stub currentUser, just call list with the specific ID
			const userCodeTasks = await repo.listCodeTasks(userWithNoItemsId);
			expect(userCodeTasks).to.be.an('array').that.is.empty;
		});

		it('should update an existing codeTask', async () => {
			const originalCodeTask = createMockCodeTask(testUserId);
			const codeTaskId = await repo.createCodeTask(originalCodeTask);
			const originalUpdateTimestamp = (await repo.getCodeTask(testUserId, codeTaskId))?.updatedAt;

			// Ensure timestamp can change
			await new Promise((resolve) => setTimeout(resolve, 5));

			const updates: Partial<CodeTask> = {
				title: 'Updated Title',
				status: 'design_review',
				error: 'An error occurred',
			};
			await repo.updateCodeTask(testUserId, codeTaskId, updates);

			const updatedCodeTask = await repo.getCodeTask(testUserId, codeTaskId);
			expect(updatedCodeTask).to.not.be.null;
			expect(updatedCodeTask?.title).to.equal(updates.title);
			expect(updatedCodeTask?.status).to.equal(updates.status);
			expect(updatedCodeTask?.error).to.equal(updates.error);
			expect(updatedCodeTask?.instructions).to.equal(originalCodeTask.instructions); // Check unchanged field
			expect(updatedCodeTask?.updatedAt).to.be.a('number');
			// Check if timestamp actually updated (might be equal in fast in-memory tests)
			if (originalUpdateTimestamp) {
				expect(updatedCodeTask?.updatedAt).to.be.greaterThanOrEqual(originalUpdateTimestamp);
			}
		});

		it('should throw an error when updating a non-existent codeTask', async () => {
			const nonExistentId = randomUUID();
			const updates = { title: 'Update Fail' };
			await expect(repo.updateCodeTask(testUserId, nonExistentId, updates)).to.be.rejectedWith(/not found/i);
		});

		it('should throw an error when updating a codeTask belonging to another user', async () => {
			const codeTaskData = createMockCodeTask(otherUserId);
			const codeTaskId = await repo.createCodeTask(codeTaskData);
			const updates = { title: 'Update Fail Other User' };
			await expect(repo.updateCodeTask(testUserId, codeTaskId, updates)).to.be.rejectedWith(/not found|authorized/i);
		});

		it('should delete an existing codeTask', async () => {
			const codeTaskData = createMockCodeTask(testUserId);
			const codeTaskId = await repo.createCodeTask(codeTaskData);

			let retrieved = await repo.getCodeTask(testUserId, codeTaskId);
			expect(retrieved).to.not.be.null;

			await repo.deleteCodeTask(testUserId, codeTaskId);

			retrieved = await repo.getCodeTask(testUserId, codeTaskId);
			expect(retrieved).to.be.null;
		});

		it('should not throw an error when deleting a non-existent codeTask', async () => {
			const nonExistentId = randomUUID();
			await expect(repo.deleteCodeTask(testUserId, nonExistentId)).to.not.be.rejected;
		});

		it('should not delete a codeTask belonging to another user', async () => {
			const codeTaskData = createMockCodeTask(otherUserId);
			const codeTaskId = await repo.createCodeTask(codeTaskData);

			// Attempt delete as testUserId
			await repo.deleteCodeTask(testUserId, codeTaskId);

			// Verify codeTask still exists for otherUserId by calling getCodeTask with otherUserId
			const retrieved = await repo.getCodeTask(otherUserId, codeTaskId);
			expect(retrieved).to.not.be.null;
		});
	});

	describe('CodeTaskPreset CRUD', () => {
		it('should create a new preset and retrieve it', async () => {
			const presetData = createMockPreset(testUserId);
			const presetId = await repo.saveCodeTaskPreset(presetData);
			expect(presetId).to.equal(presetData.id);

			// Need to list and find, as there's no getPresetById
			const userPresets = await repo.listCodeTaskPresets(testUserId);
			const retrievedPreset = userPresets.find((p) => p.id === presetId);

			expect(retrievedPreset).to.not.be.undefined;
			expect(retrievedPreset?.id).to.equal(presetData.id);
			expect(retrievedPreset?.userId).to.equal(testUserId);
			expect(retrievedPreset?.name).to.equal(presetData.name);
			expect(retrievedPreset?.config).to.deep.equal(presetData.config);
			expect(retrievedPreset?.createdAt).to.be.a('number');
		});

		it('should list presets for a user, ordered by createdAt descending', async () => {
			const preset1 = createMockPreset(testUserId, { createdAt: Date.now() - 2000 });
			const preset2 = createMockPreset(testUserId, { createdAt: Date.now() - 1000 }); // Newer
			const presetOtherUser = createMockPreset(otherUserId);

			await repo.saveCodeTaskPreset(preset1);
			await repo.saveCodeTaskPreset(preset2);
			// Save otherUser's preset directly, userId is in the object
			await repo.saveCodeTaskPreset(presetOtherUser);
			// No need to switch context, just list for testUserId
			const userPresets = await repo.listCodeTaskPresets(testUserId);
			expect(userPresets).to.be.an('array').with.lengthOf(2);
			expect(userPresets[0].id).to.equal(preset2.id); // Newest first
			expect(userPresets[1].id).to.equal(preset1.id);
		});

		it('should return an empty array when listing presets for a user with no presets', async () => {
			// No need to stub currentUser, just call list with the specific ID
			const userPresets = await repo.listCodeTaskPresets(userWithNoItemsId);
			expect(userPresets).to.be.an('array').that.is.empty;
		});

		it('should delete an existing preset', async () => {
			const presetData = createMockPreset(testUserId);
			const presetId = await repo.saveCodeTaskPreset(presetData);

			let userPresets = await repo.listCodeTaskPresets(testUserId);
			expect(userPresets.some((p) => p.id === presetId)).to.be.true;

			await repo.deleteCodeTaskPreset(testUserId, presetId);

			userPresets = await repo.listCodeTaskPresets(testUserId);
			expect(userPresets.some((p) => p.id === presetId)).to.be.false;
		});

		it('should not throw an error when deleting a non-existent preset', async () => {
			const nonExistentId = randomUUID();
			await expect(repo.deleteCodeTaskPreset(testUserId, nonExistentId)).to.not.be.rejected;
		});

		it('should not delete a preset belonging to another user', async () => {
			const presetData = createMockPreset(otherUserId);
			// Save otherUser's preset directly
			const presetId = await repo.saveCodeTaskPreset(presetData);
			// No need to switch context for delete attempt

			// Attempt delete as testUserId
			await repo.deleteCodeTaskPreset(testUserId, presetId);

			// Verify preset still exists for otherUserId by listing their presets
			const otherUserPresets = await repo.listCodeTaskPresets(otherUserId);
			expect(otherUserPresets.some((p) => p.id === presetId)).to.be.true;
		});
	});
}
